package com.ftg.learn.chapter19.jdbc.util;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.*;
import java.sql.*;
import java.util.*;
import java.util.function.Supplier;

/**
 * @author KangJx
 * @date 2021/9/3
 */
public class JdbcUtilImple<T> implements JdbcUtil<T> {

    private final String url = "jdbc:mysql://localhost:3306/learnmysql";
    private final String userName = "root";
    private final String password = "Love0913@kjx";

    T obj;
    public JdbcUtilImple(T t1) {

        this.obj = t1;
    }

    @Override
    public List<T> queryAll(T t) {
        Map<String,String> info = this.createInitTabInfo(t);
        System.out.println(info);
        String sql = "SELECT " + info.get(JdbcEnum.COLUMNNAME.toString())+" from " + info.get(JdbcEnum.TABLENAME.toString()) + " ";
        return this.jdbcSelect(sql);
    }

    @Override
    public List<T> queryAllForWhere(T t1, Supplier<String> wh, Object... obj) {
        Map<String,String> info = this.createInitTabInfo(t1);
        String sql = "SELECT " + info.get(JdbcEnum.COLUMNNAME.toString())+" from " + info.get(JdbcEnum.TABLENAME.toString()) + " ";

        String where = wh.get();

        Type[] ts = this.getClass().getGenericInterfaces();
        System.out.println(ts[0].getTypeName());

        return this.jdbcSelect(sql + where,obj);
    }

    @Override
    public int insert(T t1, Object... obj) {
        String sql = createSql(t1,'I',obj);
        System.out.println(sql);
        System.out.println(Arrays.toString(obj));
        return doDML(sql,obj);
    }

    @Override
    public int insert(T t1) {
        return 0;
    }

    @Override
    public int update(T t1) {
        return 0;
    }

    @Override
    public int delete(T t1) {
        return 0;
    }

    private String createSql(T t1, char type,Object... obj){

        Map<String,String> map = this.createInitTabInfo(t1);
        String tname = map.get(JdbcEnum.TABLENAME.toString());
        String cols = map.get(JdbcEnum.COLUMNNAME.toString());

        switch (type){
            //insert
            case 'I':
                return "insert into "+ tname +"(" + cols + ") values (" + createWhy(obj) + ")" .toUpperCase();
            case 'U':
                //判断 哪个一个是主键
                return "update " + tname +" set ";
            case 'D':
                //判断 哪个一个是主键
                return "delete from  " + tname +" where ";
            default:
                return "";
        }

    }

    /**
     * 根据 obj长度生成?,？为添加sql服务
     * @param obj  参数的个数
     * @return
     * iobj == 7
     *
     */
    private String createWhy(Object... obj){
        StringBuilder sb = new StringBuilder();
        for (int i = 0 ,len = obj.length;i < len;i++){
            sb.append("?").append(i == len - 1 ? "" : ",");
        }
        return sb.toString();
    }

    /**
     * 这是一个用来生成表当中所有列的方法
     * @param c 实例类 pojo(属性和setter)
     * @return  列名,列名，列名，....
     */
    private String createColumnName(Class c){
        StringBuilder sb = new StringBuilder();
        Field[] fs = c.getDeclaredFields();
        int index = 0;
        for(Field f : fs){
            sb.append(f.getName()).append(index++ == fs.length-1?"":",");
        }
        return sb.toString().toUpperCase();
    }

    /**
     * 通过反射类直接拿到表名
     * @param t
     * @return
     */
    private String getTableName(Class t){
        return t.getSimpleName();
    }

    /**
     * 用来返回 当前类当中所有的所有列名，以及表名
     * @param t1  当前的pojo类
     * @return  map(JdbcEnum.COLUMNNAME.toString(),列名)
     * map(JdbcEnum.TABLENAME.toString(),表名)
     */
    private Map<String,String> createInitTabInfo(T t1){
        Map<String,String> map = new HashMap<>(2);
        Class c  = t1.getClass();
        map.put(JdbcEnum.COLUMNNAME.toString(),createColumnName(c));
        map.put(JdbcEnum.TABLENAME.toString(),getTableName(c));
        return map;
    }

    /**
     * 完成数据库里面的所有dml操作
     * @param sql  语句
     * @param obj  值
     * @return     正整数 | -1 （没有影响）
     */
    private int doDML(String sql,Object...obj){
        try(
            Connection conn = DriverManager.getConnection(url,userName,password);
            PreparedStatement pstmt = conn.prepareStatement(sql);
        ){
            if(obj != null){
                for(int i = 0 ,len = obj.length;i < len;i++){
                    pstmt.setObject(i + 1 , obj[i]);
                }
            }
            return pstmt.executeUpdate();
        }catch (SQLException e) {
            e.printStackTrace();
        }
        return -1;
    }

    /**
     * 真证完成对数据操作的方法
     * @param sql  执行语句
     * @param obj  参数
     * @return   List<T></T>
     */
    private List<T> jdbcSelect(String sql,Object...obj){
        List<T> list = new ArrayList<>();
        try(
            Connection conn = DriverManager.getConnection(url,userName,password);
            PreparedStatement pstmt = conn.prepareStatement(sql);
        ){
            if(obj != null){
                for(int i = 0 ,len = obj.length;i < len;i++){
                    pstmt.setObject(i + 1 , obj[i]);
                }
            }
            //查询
            ResultSet rs = pstmt.executeQuery();
            ResultSetMetaData rsmd = rs.getMetaData();
            //所有行 创建新的emp
            while(rs.next()){
                //o - emp
                Object o = this.obj.getClass().newInstance();
                //所有列 要为emp里面的setter赋值
                for(int i = 0,len =rsmd.getColumnCount(); i < len;i++){
                    //列名也哪个东西相映射 emp 属性
                    String colIName = rsmd.getColumnName(i + 1);
                    //colIname这个属性对应的【getter/setter】
                    PropertyDescriptor pd = new PropertyDescriptor(colIName,o.getClass());
                    //setter
                    Method setter = pd.getWriteMethod();
                    //取到this.obj.getClass() - colIName字段的类型
                    String tname = rsmd.getColumnTypeName(i+1);
                    //newInstance 相当于new
                    switch (tname){
                        case "TINYINT":
                        case "SMALLINT":
                        case "MEDIUMINT":
                            setter.invoke(o,rs.getInt(i+1));
                            break;
                        case "FLOAT":
                            setter.invoke(o,rs.getFloat(i+1));
                            break;
                        case "DOUBLE":
                            setter.invoke(o,rs.getDouble(i+1));
                            break;
                        case "BIT":
                        case "BOOLEAN":
                            setter.invoke(o,rs.getBoolean(i+1));
                            break;
                        case "INT":
                        case "BIGINT":
                            setter.invoke(o,rs.getLong(i+1));
                            break;
                        default:
                            setter.invoke(o,rs.getObject(i+1));
                    }

                }
                //emp emp emp
                list.add((T)o);
            }

        }catch (SQLException | IntrospectionException | IllegalAccessException  | InvocationTargetException | InstantiationException e) {
            e.printStackTrace();
        }
        return list;
    }

}
